/*
* ============================================================================
*  Name        : sea_searchengine.cpp
*  Part of     : Finite state machine utilities
*  Description : Object implementation.
*  Version     : %version: %
*  
*
* ============================================================================
*/
#include "sea_searchengine.h"

/**
 * @file sea_searchengine.cpp
 * @brief Search engine implementation
 *
 * @ingroup AssociationUtilities
 */
using namespace sea;

// -------------------------------------------------------------------------------------------------
// Create vocabulary from the input string
// -------------------------------------------------------------------------------------------------
void SearchEngine::add_words( const char* stream )
{
    // Copy const buffer into working buffer
    int len = strlen( stream );
    if ( len == 0 )
    {
        return;
    }

    char* wbuffer = new char[ len + 1 ];
    if ( !wbuffer )
    {
        return;
    }

    std::auto_ptr< char > todelete( wbuffer );
    strncpy_s( wbuffer, len + 1, stream, len );
    
    char* sentence = wbuffer;
    char* new_sentence;
    Association init_id( 0, NULL );

    while( ( new_sentence = parse_sentence( sentence ) ) != NULL )
    {
        std::auto_ptr< char > todelete( new_sentence );
        std::auto_ptr< char > word;

        for( ;; )
        {
            word.reset( parse_word( new_sentence ) );
            if ( word.get( ) == NULL )
            {
                break;
            }

            sea::ref< List > aword;
            aword = create_word( word.get( ) );
            m_words->attach_last( aword.get( ) );
        }
    }
}

// -------------------------------------------------------------------------------------------------
// Create vocabulary from the input file
// -------------------------------------------------------------------------------------------------
void SearchEngine::add_words( String path )
{
    char* new_sentence;
    FILE* file = NULL;
    char* buffer = NULL;
    int buffer_size = 0;
    int read_bytes = 0;
    Association init_id( 0, NULL );
    std::auto_ptr< char > todelete( buffer );

    while( ( new_sentence = parse_sentence( path, file, buffer, buffer_size, read_bytes ) ) != NULL )
    {
        std::auto_ptr< char > todelete( new_sentence );
        std::auto_ptr< char > word;

        for( ;; )
        {
            word.reset( parse_word( new_sentence ) );
            if ( word.get( ) == NULL )
            {
                break;
            }
            sea::ref< List > aword;
            aword = create_word( word.get( ) );
            m_words->attach_last( aword.get( ) );
        }
    }

    if ( file )
    {
        fclose( file );
    }
}

// -------------------------------------------------------------------------------------------------
// Create vocabulary from the input string
// -------------------------------------------------------------------------------------------------
void SearchEngine::add_text( const char* stream )
{
    // Copy const buffer into working buffer
    int len = strlen( stream );
    if ( len == 0 )
    {
        return;
    }

    char* wbuffer = new char[ len + 1 ];
    if ( !wbuffer )
    {
        return;
    }

    std::auto_ptr< char > todelete( wbuffer );
    strncpy_s( wbuffer, len + 1, stream, len );
    
    char* sentence = wbuffer;
    char* new_sentence;
    Association init_id( 0, NULL );

    while( ( new_sentence = parse_sentence( sentence ) ) != NULL )
    {
        sea::ref< List > sentence;
        sentence << NEW List( init_id );

        std::auto_ptr< char > todelete( new_sentence );
        std::auto_ptr< char > word;

        for( ;; )
        {
            word.reset( parse_word( new_sentence ) );
            if ( word.get( ) == NULL )
            {
                break;
            }
            sea::ref< List > aword;
            aword = create_word( word.get( ) );
            sentence->attach_last( aword.get( ) );
        }

        m_vacabulary->attach_last( sentence.get( ) );

        // SEA_TRACE( "%s", sentence2string( sentence ).c_str( ) );
    }
}

// -------------------------------------------------------------------------------------------------
// Create a vocabulary from the input file
// -------------------------------------------------------------------------------------------------
void SearchEngine::add_text( String path )
{
    char* new_sentence;
    FILE* file = NULL;
    char* buffer = NULL;
    int buffer_size = 0;
    int read_bytes = 0;
    Association init_id( 0, NULL );
    std::auto_ptr< char > todelete( buffer );

    while( ( new_sentence = parse_sentence( path, file, buffer, buffer_size, read_bytes ) ) != NULL )
    {
        sea::ref< List > sentence;
        sentence << NEW List( init_id );

        std::auto_ptr< char > todelete( new_sentence );
        std::auto_ptr< char > word;

        for( ;; )
        {
            word.reset( parse_word( new_sentence ) );
            if ( word.get( ) == NULL )
            {
                break;
            }
            sea::ref< List > aword;
            aword = create_word( word.get( ) );
            sentence->attach_last( aword.get( ) );
        }

        if ( sentence->size( ) )
        {
            m_vacabulary->attach_last( sentence.get( ) );
            // SEA_TRACE( "%s", sentence2string( sentence ).c_str( ) );
        }
    }

    if ( file )
    {
        fclose( file );
    }
}

// -------------------------------------------------------------------------------------------------
// Get a word by 1st letter
// -------------------------------------------------------------------------------------------------
String SearchEngine::get_word( char character )
{
    Object** alphabet = get_alphabet( );
    int index = character;
    for( VIterator< > it( alphabet[ index ] ); *it; it++ )
    {
        Association id = it.get_association( );
        
        // Enumerator must be 0, that is the letter must
        // be 1st in the word
        if ( id.get_enum( ) != 0 )
        {
            continue;
        }

        List* word = id.as_enum_object< List >( );
        return word2string( word );
    }

    return String( );
}

// -------------------------------------------------------------------------------------------------
// Get all words starting with the substring
// -------------------------------------------------------------------------------------------------
vector< String > SearchEngine::get_all_words_starting_with( const char* sub_str )
{
    int len;
    if ( !sub_str || ( len = strlen( sub_str ) ) == 0 )
    {
        return vector< String >( );
    }
    vector< String > result;
    Object** alphabet = get_alphabet( );

    int index = tolower( *sub_str );
    for( VIterator< > it( alphabet[ index ] ); *it; it++ )
    {
        Association id = it.get_association( );
        
        // Enumerator must be 0, that is the letter must
        // be 1st in the word
        if ( id.get_enum( ) != 0 )
        {
            continue;
        }

        List* word = id.as_enum_object< List >( );
        String candidate = word2string( word );

        if( strncmp( candidate.c_str( ), sub_str, len ) == 0 )
        {
            result.insert( result.end( ), candidate );
        }
    }

    return result;
}

// -------------------------------------------------------------------------------------------------
// Get all words starting with the substring
// -------------------------------------------------------------------------------------------------
vector< String > SearchEngine::get_all_words_containing( const char* sub_str )
{
    if ( !sub_str || strlen( sub_str ) == 0 )
    {
        return vector< String >( );
    }
    vector< String > result;
    Object** alphabet = get_alphabet( );

    int index = tolower( *sub_str );
    map< List*, String > checker;
    for( VIterator< > it( alphabet[ index ] ); *it; it++ )
    {
        Association id = it.get_association( );
        
        List* word = id.as_enum_object< List >( );
        String candidate = word2string( word );

        size_t found = candidate.find( sub_str );
        if ( found != string::npos )
        {
            // Since it is normal for a word contains the same letter
            // more than once, the word may be in the resulting list already
            if ( checker.find( word ) == checker.end( ) )
            {
                result.insert( result.end( ), candidate );
                checker[ word ] = candidate;
            }
        }
    }

    return result;
}

// -------------------------------------------------------------------------------------------------
// Get all words starting with the substring
// -------------------------------------------------------------------------------------------------
vector< String > SearchEngine::get_all_words_ending( const char* sub_str )
{
    i::uint32 len;
    if ( !sub_str || ( len = strlen( sub_str ) ) == 0 )
    {
        return vector< String >( );
    }
    vector< String > result;
    Object** alphabet = get_alphabet( );

    int index = tolower( *sub_str );
    map< List*, String > checker;
    for( VIterator< > it( alphabet[ index ] ); *it; it++ )
    {
        Association id = it.get_association( );
        
        List* word = id.as_enum_object< List >( );
        String candidate = word2string( word );

        if ( candidate.size( ) >= len && 
             strncmp( candidate.c_str( ) + candidate.size( ) - len, sub_str, len ) == 0 )
        {
            // Since it is normal for a word contains the same letter
            // more than once, the word may be in the resulting list already
            if ( checker.find( word ) == checker.end( ) )
            {
                result.insert( result.end( ), candidate );
                checker[ word ] = candidate;
            }
        }
    }

    return result;
}

// -------------------------------------------------------------------------------------------------
// Find the word in the vocabulary
// -------------------------------------------------------------------------------------------------
sea::ref< List > SearchEngine::find_word( const char* word )
{
    int len = strlen( word );
    if ( len == 0 )
    {
        return NULL;
    }
    String comparator( word );
    std::transform( comparator.begin( ), comparator.end( ), comparator.begin( ), ::tolower );

    // Search if word already exists
    Object** alphabet = get_alphabet( );
    ref< List > the_word;

    // Check by 1st letter of the word
    int index = tolower( *word );

    // Move down along all associations for that letter
    for( VIterator< > it( alphabet[ index ] ); *it; it++ )
    {
        // Get the association { enum, List-Word }
        Association id = it.get_association( );
        
        // Enumerator must be 0, that is the letter must
        // be 1st in the word
        if ( id.get_enum( ) != 0 )
        {
            continue;
        }

        // Get registered word by Id, which is enumerated Object-List,
        // contaning the word
        the_word = id.as_enum_object< List >( );

        // Compare word size
        if ( the_word->size( ) != len )
        {
            // Nope,...
            continue;
        }

        // Compare by last letter
        if ( the_word->get_last( ).get( )->as_int( ) != ( i::uint32 )comparator[ len - 1 ] )
        {
            // Nope,...
            continue;
        }

        // 1st, last and size are the same
        String compare = word2string( the_word );
        if ( compare == comparator )
        {
            it.move_first( );
            return the_word;
        }
    }

    return NULL;
}
// -------------------------------------------------------------------------------------------------
// Create sea word ( list of letters )
// -------------------------------------------------------------------------------------------------
sea::ref< List > SearchEngine::create_word( const char* word )
{
    ref< List > the_word = find_word( word );
    if ( the_word != NULL )
    {
        return the_word;
    }

    // Create word list
    the_word << NEW List( Association( 0, NULL ) );
    if ( !word )
    {
        return NULL;
    }

    int len = strlen( word );
    Object** alphabet = get_alphabet( );

    // Construct a word
    for( int i = 0; i < len; i++ )
    {
        int index = tolower( word[ i ] );

        // Link each letter in the word
        the_word->attach_last( alphabet[ index ], true );
    }

    return the_word;
}

// -------------------------------------------------------------------------------------------------
// Get sentence from the file
// -------------------------------------------------------------------------------------------------
inline char* SearchEngine::parse_sentence( String path, FILE*& file, char*& buffer, int& buffer_size, int& read_size )
{
    if ( !buffer )
    {
        buffer = new char[ FILE_CLUSTER_SZ ];
        if ( !buffer )
        {
            ASSERT_DEBUG( false );
            return NULL;
        }
    }

    if ( !file ) 
    {
        int rc = fopen_s( &file, path.c_str( ), "rb" );
        if ( rc )
        {
            return NULL;
        }

        buffer_size = 0;
        read_size = 0;
    }

    char* output = new char[ SENTENCE_MAX_SZ ];
    if ( !output )
    {
        fclose( file );
        delete buffer;

        return NULL;
    }
    char* result = output;

    for ( ;; )
    {
        char* tmp_buffer;
        if ( buffer_size < read_size )
        {
            tmp_buffer = buffer;
            for ( ; buffer_size < read_size; buffer_size++ )
            {
                char letter = tmp_buffer[ buffer_size ];
                if ( is_end_of_sentence( letter ) || result - output == SENTENCE_MAX_SZ )
                {
                    buffer_size++;
                    *output = 0;
                    return result;
                }
                else 
                if ( is_char( letter ) )
                {
                    *output = letter;
                    output++;
                }
            }
        }
        else
        {
            tmp_buffer = buffer;
        }

        buffer_size = 0;
        read_size = fread_s( buffer, FILE_CLUSTER_SZ, 1, FILE_CLUSTER_SZ, file );
        if ( !read_size )
        {
            break;
        }
    }

    if ( result == output )
    {
        delete result;
        return NULL;
    }

    *output = 0;
    return result;
}

// -------------------------------------------------------------------------------------------------
// Get sentance from the file
// -------------------------------------------------------------------------------------------------
char* SearchEngine::parse_sentence( char*& stream )
{
    stream = skip_spaces( stream );
    char* end = get_end_of_sentence( stream );
    if ( end == stream )
    {
        return NULL;
    }

    int len = end - stream + 1;
    char* new_sentence = new char[ len ];
    if ( !new_sentence )
    {
        return NULL;
    }

    strncpy_s( new_sentence, len, stream, len - 1 );
    new_sentence[ len - 1 ] = 0;

    stream += len;

    return new_sentence;
}

// -------------------------------------------------------------------------------------------------
// Extract word from the input string
// -------------------------------------------------------------------------------------------------
char* SearchEngine::parse_word( char*& stream )
{
    if( stream == 0 || *stream == 0 )
    {
        return NULL;
    }

    stream = skip_spaces( stream );
    char* end = get_end_of_word( stream );

    int len = end - stream + 1;
    if( len == 1 )
    {
        return NULL;
    }

    char* new_word = new char[ len ];
    if( !new_word )
    {
        return NULL;
    }
    strncpy_s( new_word, len, stream, len - 1 );
    new_word[ len - 1 ] = 0;

    // SEA_TRACE( "%s", newWord );

    stream = end;

    return new_word;
}

// -------------------------------------------------------------------------------------------------
// Convert the sentance to a string
// -------------------------------------------------------------------------------------------------
inline String SearchEngine::sentence2string( List* sentence )
{
    if ( !sentence )
    {
        return String( );
    }

    String str;
    for( HIterator< List, Right > it( sentence ); *it; ++it ) 
    {
        str += word2string( *it );
        str += " ";
    }
    return str;
}

// -------------------------------------------------------------------------------------------------
// Convert the word to a string
// -------------------------------------------------------------------------------------------------
inline String SearchEngine::word2string( List* word )
{
    if ( !word )
    {
        return String( );
    }

    ASSERT_ALWAYS( word->size( ) < WORD_MAX_SZ );
    char str[ WORD_MAX_SZ ];
    int i = 0;
    for( HIterator< > it( word ); *it; ++it ) 
    {
        str[ i++ ] = it->as_int( );
    }
    str[ i ] = 0;
    return String( str );
}

// -------------------------------------------------------------------------------------------------
// Destroy alphabet
// -------------------------------------------------------------------------------------------------
void SearchEngine::destroy_alphabet( )
{
    Object** alphabet = get_alphabet( );
    for( int i = 0; i < ALPHABET_SZ; i++ ) 
    {
        alphabet[ i ]->release_ref( );
        alphabet[ i ] = 0;
    }
}

// -------------------------------------------------------------------------------------------------
// Create/get alphabet
// -------------------------------------------------------------------------------------------------
Object** SearchEngine::get_alphabet( )
{
    static Object* alphabet[ ALPHABET_SZ ] = { NULL };

    if( *alphabet == NULL )
    {
        for( i::int32 i = 0; i < ALPHABET_SZ; i++ )
        {
            alphabet[ i ] = NEW Object( i );
        }
    }
    return alphabet;
}

// -------------------------------------------------------------------------------------------------
// Print stats
// -------------------------------------------------------------------------------------------------
void SearchEngine::stats( )
{
    Object** alphabet = get_alphabet( );

    for( int i = 0; i < ALPHABET_SZ; i++ )
    {
        if ( alphabet[ i ]->refs( ) == 1 )
        {
            continue;
        }

        int words1st = 0;
        int wordslast = 0;
        int inword = 0;

        for( VIterator< Object, Either > it( alphabet[ i ] ); *it; it++ )
        {
            Association id = it.get_association( );
            List* word = id.as_enum_object< List >( );
            if ( id.get_enum( ) == 0 )
            {
                words1st++;
            }
            
            if ( id.get_enum( ) == word->size( ) - 1 )
            {
                wordslast++;
            }
            else
            {
                inword++;
            }
        }

        if ( words1st == 0 && wordslast == 0 && inword == 0 )
        {
            words1st = 1;
        }

        SEA_TRACE( "Letter %c: 1st:%d last:%d middle:%d -> %d", 
            alphabet[ i ]->as_int( ), 
            words1st, 
            wordslast, 
            inword, 
            words1st + wordslast + inword );
    }
}

// -------------------------------------------------------------------------------------------------
